import std.stdio;
import std.stream;
import std.string;
import std.conv;
import std.file;

enum Opcode { CON, ADD, SUB, MUL, DIV, GOD, JPO, GTO, POP, PRI, RED, HAL }

class Instruction {
  Opcode opcode;
  int operand;

  this(string s) {
    string t=strip(s);

    switch(t) {
    case "add":
      opcode=Opcode.ADD;
      break;
    case "sub":
      opcode=Opcode.SUB;
      break;
    case "mul":
      opcode=Opcode.MUL;
      break;
    case "div":
      opcode=Opcode.DIV;
      break;
    case "god":
      opcode=Opcode.GOD;
      break;
    case "jpo":
      opcode=Opcode.JPO;
      break;
    case "gto":
      opcode=Opcode.GTO;
      break;
    case "pop":
      opcode=Opcode.POP;
      break;
    case "pri":
      opcode=Opcode.PRI;
      break;
    case "red":
      opcode=Opcode.RED;
      break;
    case "hal":
      opcode=Opcode.HAL;
      break;
    default:
      try {
        opcode=Opcode.CON;
        operand=toInt(t);
      } catch(ConvError e) {
        throw new Error("unknown instruction: "~s);
      }
    }
  }
}

class Vm {
  const default_stack_size=1000;

  Instruction[] code;
  int[] stack;
  int pc, sp;
  int step, max;
  bool silent;
  int ecode;

  this(string fname,bool b) {
    stack.length=default_stack_size;
    silent=b;
    File f=new File(fname);

    while(!f.eof()) {
      code~=new Instruction(cast(string)f.readLine());
    }
    f.close();
  }

  void chkstack() {
    if(sp>=stack.length) {
      stack.length=stack.length*2+1;
    }
  }

  void run() {
    writefln("*** START ***");
    try{
run:  while(true) {
        if(pc<0 || pc>=code.length) {
          throw new Error("cannot fetch next instruction");
        }

        switch(code[pc].opcode) {
        case Opcode.CON:
          chkstack();
          stack[sp]=code[pc].operand;
          sp++; pc++;
          break;
        case Opcode.ADD:
          if(sp<2) throw new Error("add: stack is empty");
          stack[sp-2]=stack[sp-2]+stack[sp-1];
          sp--; pc++;
          break;
        case Opcode.SUB:
          if(sp<2) throw new Error("sub: stack is empty");
          stack[sp-2]=stack[sp-2]-stack[sp-1];
          sp--; pc++;
          break;
        case Opcode.MUL:
          if(sp<2) throw new Error("mul: stack is empty");
          stack[sp-2]=stack[sp-2]*stack[sp-1];
          sp--; pc++;
          break;
        case Opcode.DIV:
          if(sp<2) throw new Error("div: stack is empty");
          int a,b;
          a=stack[sp-2]; b=stack[sp-1];
          try {
            if(a>=0) {
              stack[sp-2]= b>0 ? a/b : -(a/-b);
            } else {
              stack[sp-2]= b>0 ? -(-a/b) : -a/-b;
            }
          } catch(Exception e) {
            throw new Error("div: division by zero");
          }
          sp--; pc++;
          break;
        case Opcode.GOD:
          if(sp==0) throw new Error("god: stack is empty");
          if(stack[sp-1]<0 || sp-1<=stack[sp-1]) throw new Error("god: stack out of bounds");
          stack[sp-1]=stack[stack[sp-1]];
          pc++;
          break;
        case Opcode.JPO:
          if(sp<2) throw new Error("jpo: stack is empty");
          if(stack[sp-2]>0) {
            pc=stack[sp-1];
          } else {
            pc++;
          }
          sp-=2;
          break;
        case Opcode.GTO:
          if(sp<2) throw new Error("gto: stack is empty");
          if(stack[sp-1]<0 || sp-2<=stack[sp-1]) throw new Error("gto: stack out of bounds");
          stack[stack[sp-1]]=stack[sp-2];
          sp-=2;
          pc++;
          break;
        case Opcode.POP:
          if(sp==0) throw new Error("pop: stack is empty");
          sp--; pc++;
          break;
        case Opcode.PRI:
          if(sp==0) throw new Error("pri: stack is empty");
          writefln(stack[sp-1]);
          sp--; pc++;
          break;
        case Opcode.RED:
          chkstack();
          if(!silent) writefln("input an integer?");
          try {
            stack[sp]=toInt(strip(readln()));
          } catch(ConvError e) {
            throw new Error("input is not an integer!");
          }
          sp++; pc++;
          break;
        case Opcode.HAL:
          if(sp==0) throw new Error("hal: stack is empty");
          ecode=stack[sp-1];
          sp--;
          break run;
        default:
          throw new Error("never reach");
        }

        if(max<sp) max=sp;
        step+=1;
      }

      if(ecode!=0) {
        writefln("*** HALT [%d] *** (%d steps, %d stacks)",ecode,step,max);
      } else {
        writefln("*** HALT *** (%d steps, %d stacks)",step,max);
      }
    } catch(Exception e) {
      writefln("*** ERROR [%s] *** (%s steps, %s stacks)",e,step,max);
    }
  }
}

void main(string[] args) {
  bool silent=false;
  string fname="";

  if(args.length==2) {
    if(args[1]=="-s") {
      silent=true;
    } else {
      fname=args[1];
    }
  } else if(args.length==3 && args[1]=="-s") {
    silent=true;
    fname=args[2];
  } else if(args.length==3 && args[2]=="-s") {
    silent=true;
    fname=args[1];
  } else if(args.length!=1) {
    writefln("usage: depro [-s] [code]");
    return 1;
  }

  if(fname=="") {
    fname="epro.code";
  } else if(!exists(fname) || !isfile(fname)) {
    fname=fname~".code";
  }

try {
  Vm vm=new Vm(fname,silent);
  vm.run();
  } catch(Exception e) {
    writefln(e);
    return 1;
  }
}
